#include <drivers/gpio.h>
#include <drivers/platform.h>
#include <kernel/syslog.h>
#include <list.h>
#include <types.h>
#include <string.h>
#include <irqnum.h>
#include <drivers/irq.h>
#include <rp2040.h>
#include <resets.h>
#include <config.h>
#include <iomode.h>


struct sio_hw {
	uint cpuid;
	uint gpio_in;
	uint gpio_hi_in;
	uint unused;
	uint gpio_out;
	uint gpio_out_set;
	uint gpio_out_clr;
	uint gpio_out_xor;
	uint gpio_oe;
	uint gpio_oe_set;
	uint gpio_oe_clr;
	uint gpio_oe_xor;
};


struct io_bank0_hw {
	struct gpio {
		uint status;
		uint ctrl;
	} gpio[30];
};


struct pads_bank0_hw {
	uint voltage_select;
	uint gpio[30];
	uint swclk;
	uint swd;
};


#define sio		((volatile struct sio_hw*)SIO_BASE)
#define io		((volatile struct io_bank0_hw*)IO_BANK0_BASE)
#define pads 	((volatile struct pads_bank0_hw*)PADS_BANK0_BASE)


#define PADS_GPIO_OD				(1 << 7)
#define PADS_GPIO_IE				(1 << 6)
#define PADS_GPIO_PUE				(1 << 3)
#define PADS_GPIO_PDE				(1 << 2)


static void rp2040_init(int16_t pin)
{
	io->gpio[pin].ctrl = IO_MODE_SIO;
}


static void rp2040_set_clock(int16_t pin, uint8_t status)
{
	if(status) {
		reset_release_wait(RESET_IO_BANK0);
	}
	else {
		reset_release(RESET_IO_BANK0);
	}
}


/**
 * MUX function:
 * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
 * |    high 8bit      |   low 8bit    |
 * |      pin mode     |   function    |
 * 
 */
static void rp2040_set_mux(int16_t pin, uint16_t mux)
{
	uint8_t mode = (mux >> 8) & 0xFF;
	uint8_t func = mux & 0xFF;

	io->gpio[pin].ctrl = func;
	pads->gpio[pin] = mode;
	sio->gpio_oe_set = 1 << pin;
}


static void rp2040_set_dir(int16_t pin, uint8_t flag)
{
	if (flag) {
		sio->gpio_oe_set = 1 << pin;
		pads->gpio[pin] = 0;
	} else {
		sio->gpio_oe_clr = 1 << pin;
		pads->gpio[pin] = PADS_GPIO_IE | PADS_GPIO_OD;
	}
}


static void rp2040_set_drv(int16_t pin, uint8_t drv)
{
    GPIO_SYSLOG_WARN("%d, %d: this operation is not supported", pin, drv);
}


static void rp2040_set_pulldown(int16_t pin, uint8_t flag)
{
	uint reg = pads->gpio[pin];

	if (flag) {
		reg |= PADS_GPIO_PDE;
	} else {
		reg &= (~PADS_GPIO_PDE);
	}
	pads->gpio[pin] = reg;
}


static void rp2040_set_pullup(int16_t pin, uint8_t flag)
{
	uint reg = pads->gpio[pin];

	if (flag) {
		reg |= PADS_GPIO_PUE;
	} else {
		reg &= (~PADS_GPIO_PUE);
	}
	pads->gpio[pin] = reg;
}


static void rp2040_set_value(int16_t pin, uint8_t value)
{
	if (value)
		sio->gpio_out_set = 1 << pin;
	else
		sio->gpio_out_clr = 1 << pin;
}


static int rp2040_get_value(int16_t pin)
{
	return sio->gpio_in & (1 << pin);
}


static int rp2040_set_exti(int16_t pin, int flag)
{
    SYSLOG_INFO("not support this operation");
    return IO_BANK0_IRQNUM;
}


struct gpio_ops rp2040_ops = {
	.init = rp2040_init,
	.set_clock = NULL,
    .set_mux = rp2040_set_mux,
    .set_dir = rp2040_set_dir,
    .set_drv = rp2040_set_drv,
	.set_pulldown = rp2040_set_pulldown,
	.set_pullup = rp2040_set_pullup,
    .set_value = rp2040_set_value,
    .get_value = rp2040_get_value,
    .set_exti = rp2040_set_exti,
};


struct gpio_desc rp2040_gpio = {
    .name = "rp2040-gpio",
    .npins = CONFIG_GPIO_PINS,
    .ops = &rp2040_ops,
};


static int rp2040_gpio_register(void)
{
    gpio_desc_register(&rp2040_gpio);
    return 0;
}


base_initcall(rp2040_gpio_register);
